/**
 * 
 */
package orderProcessingProcess.server;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import javax.jws.WebService;

import com.sun.xml.internal.ws.developer.Stateful;

import orderProcessingProcess.exceptions.faults.ItemUnavailableFault;
import orderProcessingProcess.exceptions.faults.UnknownItemFault;
import orderProcessingProcess.exceptions.faults.UnknownOrderFault;
import orderProcessingProcess.model.Customer;
import orderProcessingProcess.model.Item;
import orderProcessingProcess.model.Order;
import orderProcessingProcess.util.AIC_log;

/**
 * @author charly
 *
 */
@Stateful
@WebService(endpointInterface="orderProcessingProcess.server.OrderManagementService")
public class OrderManagementServiceImpl implements OrderManagementService
{
	private List<Item> available_items_list;
	private List<Item> shipping_list;
	private List<Item> shipped_list;
	private Integer order_id;
	
	private String type = "OrderManagementService";
	
	public OrderManagementServiceImpl()
	{
		this.available_items_list = new ArrayList<Item>();
		this.shipping_list = new ArrayList<Item>();
		this.shipped_list = new ArrayList<Item>();
		order_id = 1;
	}
	
	/**
	 * check_availability
	 * takes a single item and checks if the item is available. The operation
	 * returns true if the item is available, it means the item is in the available_items_list 
	 * and the quantity is high enough , false otherwise (available quantity too low), and throws an UnknownItemFault if
	 * the item does not exist at all.
	 * @param: item, type Item
	 * @throws: UnknownItemFault
	 */
	public synchronized boolean check_availability(Item item) throws UnknownItemFault 
	{	
		AIC_log.printerr(type, "Item - id: " + item.getProduct_id());
		AIC_log.printerr(type, "Item - qunatity: " + item.getQuantity());

		for(Item availableItem : this.available_items_list)
		{
			if(availableItem.equals(item))
			{
				AIC_log.print(type, "check_availability() - Item found, availability: " + (availableItem.getQuantity() >= item.getQuantity() ? true : false));
				return availableItem.getQuantity() >= item.getQuantity() ? true : false;
			}
		}
		AIC_log.printerr(type, "The item is unknown!");
		throw new UnknownItemFault("[OrderManagementService] " + this.getDate() + " The item is unknown!");
	}

	/**
	 * place_order
	 * takes an array of items and a customer, and "reserves" the items for 
	 * the customer. For every Item of the order, the quantity of the according item from the available_items_list gets decreased.
	 * If one of the items is not available it raises an ItemUnavailableFault.
	 * There is also a timer which schedules the ShippingTask. This task moves the items from the order to the shipped_list and removes
	 * it from the shipping list.
	 * precondition: the array items contains only items that exist in the available_items_list, therefore a UnknownItemFault can not occur.
	 * @param Array of Item, customer of type Customer
	 * @throws ItemUnavailableFault
	 */
	public synchronized Order place_order(Item[] items, Customer customer) throws ItemUnavailableFault 
	{

		Order order = new Order();
		order.setId(this.order_id.toString());
		this.order_id++;
		order.setCustomer(customer);
		order.setOrderDate(Calendar.getInstance().getTime());
		
		//before place the order check if all items are available, otherwise throw ant ItemUnavailableFault
		for(int i = 0; i < items.length; i++)
		{
			try 
			{
				if(!this.check_availability(items[i]))
				{
					AIC_log.printerr(type, "The item with id = " + items[i].getProduct_id() + " is unavailable!");
					throw new ItemUnavailableFault("[OrderManagementService] " + this.getDate() + " The item with id = " + items[i].getProduct_id() + " is unavailable!");
				}
			}
			catch (UnknownItemFault e) 
			{
				AIC_log.printerr(type, "The item with id = " + items[i].getProduct_id() + " is unknown!");
				throw new ItemUnavailableFault("[OrderManagementService] " + this.getDate() + " The item with id = " + items[i].getProduct_id() + " is unknown!");
			}
		}	
		
		for(int i = 0; i < items.length; i++)
		{
			for(Item item : this.available_items_list)
			{
				if(item.equals(items[i]))
				{
					order.getItems().add(items[i]);
					items[i].setOrder(order);
					this.shipping_list.add(items[i]);
					item.setQuantity(item.getQuantity() - items[i].getQuantity());
					break;
				}
			}
		}
		AIC_log.print(type, "place_order() done.");

		//java.util.Timer: This class is thread-safe: multiple threads can share a single Timer object without the need for external synchronization.
		Timer timer = new Timer();
		timer.schedule(new ShippingTask(order), ((long)(((Math.random()*(10 - 5)) + 5)*1000)));

		return order;
	}
	
	
	class ShippingTask extends TimerTask 
	{
		
		private Order order;
		
		public ShippingTask(Order order)
		{
			this.order = order;
		}
		
	    public void run() {
	    	AIC_log.print(type, "ShippingTask.run() Shipping in progress...");
	    	AIC_log.print(type, "ShippingTask.run() Size of shipping list: " + shipping_list.size());
	    	AIC_log.print(type, "ShippingTask.run() Size of shipped list: " + shipped_list.size());

	    	for(Item item : order.getItems())
	    	{
	    		shipping_list.remove(item);
	    		shipped_list.add(item);
	    	}	      
	    	AIC_log.print(type, "ShippingTask.run() Size of shipping list: " + shipping_list.size());
	    	AIC_log.print(type, "ShippingTask.run() Size of shipped list: " + shipped_list.size());
	    	AIC_log.print(type, "ShippingTask.run() Shipping finished!");
	    }
	}
	

	/**
	 * is_finished
	 * checks if all items of a specific order are finished (-> if they are all in the shipped_list)
	 * returns false if at least one item of the order is still in the shipping_list
	 * throws an exception if there is no item of the specific order in the shipping_list and at least one item
	 * of the order is also not present in the shipped_list.
	 * @param order, type Order
	 * @throws UnknownOrderFault
	 */
	public synchronized boolean is_finished(Order order) throws UnknownOrderFault 
	{

		if(order.getItems().size() == 0)
		{
			AIC_log.printerr(type, "is_finished() Order with ID = " + order.getId() + " is unknown or at least one item is missing!");
			throw new UnknownOrderFault("[OrderManagementService] " + this.getDate() + " is_finished() The order with id = " + order.getId() + " is unknown or at least one item is missing!");
		}

		for(Item item : this.shipping_list)
		{
			if(item.getOrder().getId().equals(order.getId()))
			{
				AIC_log.print(type, "is_finished() Order not ready");
				return false;
			}
		}
		
		boolean check = false;
		
		for(Item item : order.getItems())
		{
			for(Item shippedItems : this.shipped_list)
			{				
				if(item.equals(shippedItems) & order.getId().equals(shippedItems.getOrder().getId()))
				{
					check = true;
				}
			}
			if(!check)
			{
				AIC_log.printerr(type, "is_finished() The order with id = " + order.getId() + " is unknown or at least one item from the order is missing!");
				throw new UnknownOrderFault("[OrderManagementService] " + this.getDate() + " is_finished() The order with id = " + order.getId() + " is unknown or at least one item from the order is missing!");
			}
			check = false;
		}
		AIC_log.print(type, "is_finished() Order with ID = " + order.getId() + " is ready");
		return true;
	}


	/**
	 * insert_test_data
	 * inserts a list of test data to the available_items_list
	 */
	public synchronized void insert_test_data(List<Item> items) 
	{

		this.available_items_list = items; 
		this.order_id = 1;
		AIC_log.print(type, "insert_test_data() Size of available items list after insert test data: " + this.available_items_list.size());
	
		//reset the other lists in order to provide a clean test environment
		this.shipping_list.clear();

		AIC_log.print(type, "insert_test_data() Size of shipping list: " + this.shipping_list.size());
		this.shipped_list.clear();

		AIC_log.print(type, "insert_test_data() Size of shipped list: " + this.shipped_list.size());
	}
	
	/**
	 * getDate
	 * formats the date
	 * @return
	 */
	private String getDate()
	{
		return (new SimpleDateFormat("HH:mm:ss")).format(Calendar.getInstance().getTime());
	}
}
